package net.ib.mtalk.network.task;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.UUID;

import net.ib.mntalk.protocol.Common;
import net.ib.mntalk.protocol.Common.FileKey;
import net.ib.mntalk.protocol.Common.UUIDMsg;
import net.ib.mntalk.protocol.File.DownloadReq;
import net.ib.mntalk.protocol.File.DownloadRes;
import net.ib.mntalk.protocol.File.ImageOption;
import net.ib.mntalk.protocol.File.ValidateDownloadReq;
import net.ib.mntalk.protocol.File.ValidateDownloadRes;
import net.ib.mntalk.protocol.client.Database;
import net.ib.mntalk.protocol.client.Database.FileTransferInfo;
import net.ib.mtalk.constant.HandlerMsgConst;
import net.ib.mtalk.constant.MTalkConstant;
import net.ib.mtalk.constant.NetworkConstant.FileServer;
import net.ib.mtalk.database.helper.BuddyDatabaseHelper;
import net.ib.mtalk.database.helper.ProfileDatabaseHelper;
import net.ib.mtalk.network.FileClient;
import net.ib.mtalk.network.MethodId;
import net.ib.mtalk.network.NetResultReceiver;
import net.ib.mtalk.network.TcpEnvelope;
import net.ib.mtalk.util.GpbSupport;
import net.ib.mtalk.util.MTalkLogWriter;
import net.ib.mtalk.util.ProfileImageLoader;
import android.content.Context;
import android.os.Handler;
import android.os.Message;

import com.google.protobuf.micro.MessageMicro;

public class ProfileDownloadTask extends AsyncNetTask implements NetResultReceiver.Receiver {

	/* 소스 매커니즘에 필요한 자원 */
	/** AsyncNetTask에서 콜백 구조는 활용하지 않고, 스레드 자원만 빌려 쓴다. 따라서 자체 콜백이 있어야 함. */
	NetResultReceiver mCallback;
	
	/* 프로필의 candidate key */
	/** 이 프로필 사진 주인의 UUID */
	protected UUIDMsg mProfileUuid;

	/* 관리 정보 */
	/** 전송 상태 정보 */
	FileTransferInfo mTransferInfo;
	
	static String MODE_DOWNLOAD = "0";
	/**
	 * 새로 객체를 만들고 의미상의 기본값으로 transferInfo를 세팅한다.
	 */
	public static FileTransferInfo newFileTransferInfo(FileKey fileKey) {
		FileTransferInfo transferInfo = new FileTransferInfo();
		// null 대용 값
		transferInfo.setSliceNo(-1);
		transferInfo.setNumOfSlice(0);
		transferInfo.setSliceSize(FileServer.SLICE_DEFAULT_SIZE);
		// 정해진 초기값
		transferInfo.setState(Database.NONE);
		transferInfo.setWorking(false);
		// 반드시 있어야 하는 정보
		transferInfo.setFileKey(fileKey);	// null이면 여기에서 에러가 난다.
		transferInfo.setState(Database.RECEIVED_TOKEN);	// null이 아니어야 이 구문이 수행되겠지.
		return transferInfo;
	}
	
	/**
	 * 비휘발성 저장소에 전송 정보를 저장한다.
	 * DB에 넣고 제 3자는 커서로 진행상황을 감시할 수 있는 형태가 될 것으로 보인다.
	 */
	protected void updateNonvolatileInfo() {
		ProfileDatabaseHelper.setFileTransferInfo(mContext, mProfileUuid, mTransferInfo, MODE_DOWNLOAD);
	}
	
	/**
	 * 비휘발성 저장소로부터 전송 정보를 획득한다.
	 */
	protected void extractNonvolatileInfo() {
		mTransferInfo = ProfileDatabaseHelper.getFileTransferInfo(mContext, mProfileUuid, MODE_DOWNLOAD);
	}
	
	/* DB 쿼리 등을 반복하지 않기 위해, 추출해 낸 부가 정보를 이 변수들에 캐시한다 */
	/** 로컬 파일 정보 */
	private File mFile = null;
	/** 로컬 파일 정보 */
	protected File getFile() {
		if (mFile == null) {
			mFile = new File(mTransferInfo.getFileUri());
		}
		return mFile;
	}

	/** 다운로드 옵션 */
	ImageOption mImageOption;
	/** 버디인지 아닌지*/
	int isBuddy = 0;

	/**
	 * {@link #FileDownloadTask(Context, Handler, UUID, UUIDMsg, int, ImageSize, int, FileTransferInfo)
	 * FileDownloadTask(context, uiHandler, clientViewUuid, messageUuid, requestId,
	 * <i>newTransferInfo</i>(fileKey), imageSize, imageEffect)}와 같다.
	 */
	public ProfileDownloadTask(Context context, Handler uiHandler, UUIDMsg profileUuid,
			FileKey fileKey, ImageOption imageOption, int imageEffect) {
		this(context, uiHandler, profileUuid, newFileTransferInfo(fileKey), imageOption);
	}

	public ProfileDownloadTask(Context context, Handler uiHandler, UUIDMsg profileUuid,
			FileKey fileKey, ImageOption imageOption, int imageEffect, int isBuddy) {
		this(context, uiHandler, profileUuid, newFileTransferInfo(fileKey), imageOption);
		this.isBuddy = isBuddy;
	}

	/**
	 * 새로운 작업을 만든다.
	 * @param context
	 * @param uiHandler 메시지 수신에 쓰이는 UI 핸들러
	 * @param transferInfo 전송 상황 정보
	 * @param imageSize 다운로드 옵션. null 이면 세팅 안 함.
	 * @param imageEffect enum GPB.File.ImageEffect. 다운로드 옵션. 0보다 작은 수를 주면 아예 세팅 안 함.
	 * @param filekey 서버에 의해 발급된 것
	 */
	public ProfileDownloadTask(Context context, Handler uiHandler, UUIDMsg profileUuid,
			FileTransferInfo transferInfo, ImageOption imageOption) {
		super(context, uiHandler);

		mProfileUuid = profileUuid;
		
		mTransferInfo = transferInfo;	// FileKey는 반드시 있어야 한다.
		
		mImageOption = imageOption;
	}

	@Override
	protected TcpEnvelope onPreExecute() {
		mCallback = new NetResultReceiver(new Handler(), this);
		startWork();
		return null;
	}

	/**
	 * 이 Task는 여러 개의 쿼리를 동반하므로 이 함수는 사용되지 않는다.
	 * @see net.ib.mtalk.network.task.AsyncNetTask#onPostExecute(int, java.lang.Object, java.lang.Object)
	 */
	@Override
	protected void onPostExecute(int resultCode, Object sentObject, Object receivedObject) {
		// do nothing
	}

	/**
	 * 네트워크 응답을 적절한 메소드로 연결해준다.
	 * @see net.ib.mtalk.network.NetResultReceiver.Receiver#onNetResult(int, java.lang.Object, java.lang.Object)
	 */
	@Override
	public void onNetResult(int resultCode, Object sentObject, Object receivedObject) {
		try {
			MessageMicro message = ((TcpEnvelope)receivedObject).getMessage();
			if (message instanceof ValidateDownloadRes) {	// 파일 키 유효화 결과
				validationResultReceived((ValidateDownloadRes) message);
			} else if(message instanceof DownloadRes) {
				downloadResultReceived((DownloadRes)message);
			}
		} catch(Exception e) {
			MTalkLogWriter.e(e);
			mTransferInfo.setWorking(false);
			// 상태 저장. 에러이므로 반드시.
			updateNonvolatileInfo();
			mUiHandler.sendMessageAtFrontOfQueue(mUiHandler.obtainMessage(HandlerMsgConst.HANDLER_PROFILE_DOWNLOAD_FAIL));
		}
	}

	@Override
	public void onNetError(int resultCode, Object sentObject) {
		MTalkLogWriter.e("FileDownloadTask.onNetError(): network fail");
		mTransferInfo.setWorking(false);
		// 상태 저장. 에러이므로 반드시.
		updateNonvolatileInfo();
		mUiHandler.sendMessageAtFrontOfQueue(mUiHandler.obtainMessage(HandlerMsgConst.HANDLER_PROFILE_DOWNLOAD_FAIL));
	}
	
	/**
	 * 네트워크 쿼리 체인을 시작한다.
	 */
	protected void startWork() {
		mTransferInfo.setWorking(true);
		// 상태 저장. 유저는 버튼을 눌렀다.
		updateNonvolatileInfo();
		if (mTransferInfo.getState() == Database.RECEIVED_TOKEN) {
			vaidateFileKey(mTransferInfo.getFileKey(), mImageOption);	// 무조건 키가 있어야 시작한다.
		} else if (mTransferInfo.getState() == Database.VALIDATED) {
			downloadEachSlice(mTransferInfo.getSliceNo()+1, mTransferInfo.getNumOfSlice(), mTransferInfo.getSliceSize());
		} else if (mTransferInfo.getState() == Database.SENDING_SLICE) {	// 보내는 중이었는데 재전송 요청이 된 것이다.
			if (mTransferInfo.getSliceNo()+1 < mTransferInfo.getNumOfSlice()) {	// 보낼 게 남았다면
				downloadEachSlice(mTransferInfo.getSliceNo()+1, mTransferInfo.getNumOfSlice(), mTransferInfo.getSliceSize());
			} else {	// 파일 전체 다 받았다면
				downloadFinished();
			}
		} else if (mTransferInfo.getState() == Database.SENDING_MESSAGE) {
			// 이미 끝난 작업에 대한 요청이다. 그냥 죽자.
			mTransferInfo.setWorking(false);
		}
	}

	/**
	 * 파일 키에 대한 다운로드 유효화 요청
	 * @param fileKey
	 */
	protected void vaidateFileKey(FileKey fileKey, ImageOption imageOption) {
		// 요청 구성
		ValidateDownloadReq validateDownloadReq = new ValidateDownloadReq();
		validateDownloadReq.setFileKey(fileKey);
		// envelope 구성
		TcpEnvelope tcpEnvelope = new TcpEnvelope.Builder()
		.setMethodId(MethodId.FileServer.File.ValidateDownload)
		.setType(TcpEnvelope.REQUEST)
		.setMessage(validateDownloadReq)
		.setCallback(mCallback)
		.setExpireTime(5*1000)
		.build();
		// 발송
		FileClient.send(tcpEnvelope);
		MTalkLogWriter.v("FileDownloadTask.vaidateFileKey(): key validation requested");
	}

	/**
	 * 파일 키에 대한 다운로드 유효화 요청의 결과
	 * @param res
	 */
	protected void validationResultReceived(ValidateDownloadRes res) {
		int responseCode = res.getResponseCode();
		switch (responseCode) {
		case Common.SUCCESS:
			MTalkLogWriter.d("FileDownloadTask.validationResultReceived(): key validation success");
			mTransferInfo.setSliceSize(res.getSliceSize());
			mTransferInfo.setNumOfSlice(res.getSliceCount());
			// no break
		case Common.FILE_EXISTS:
			String filePath = readyTargetFilePath(res.getFileKey().getFileName());	// 파일 이름은 어떻게 정해야 하는가? 게다가 이것은 저장되어야 한다.
			if (filePath == null) {
				mTransferInfo.setWorking(false);
				// 상태 저장. 에러이므로 반드시.
				updateNonvolatileInfo();
				mUiHandler.sendMessageAtFrontOfQueue(mUiHandler.obtainMessage(HandlerMsgConst.HANDLER_PROFILE_DOWNLOAD_FAIL));
				return;
			}
			mTransferInfo.setFileUri(filePath);
			mTransferInfo.setState(Database.VALIDATED);
			// 상태 저장.
			updateNonvolatileInfo();
			getFile();	// 미리 파일 객체를 만들 수 있으면 만들어 둔다.

			downloadEachSlice(mTransferInfo.getSliceNo()+1, mTransferInfo.getNumOfSlice(), mTransferInfo.getSliceSize());
			break;
		default:
			MTalkLogWriter.e("FileDownloadTask.validationResultReceived(): key validation fail. code="+responseCode);
			mTransferInfo.setWorking(false);
			// 상태 저장. 에러이므로 반드시.
			updateNonvolatileInfo();
			mUiHandler.sendMessageAtFrontOfQueue(mUiHandler.obtainMessage(HandlerMsgConst.HANDLER_PROFILE_DOWNLOAD_FAIL));
			return;	// 중단
		}
	}
	
	/**
	 * @return 기록할 파일의 경로.
	 * 리턴 시점에 아직 파일은 만들어지지 않았고 중간 디렉토리는 만들어졌다.
	 */
	private String readyTargetFilePath(String fileName) {
		try {
			String state = android.os.Environment.getExternalStorageState();
		    if(!state.equals(android.os.Environment.MEDIA_MOUNTED))  {
		        throw new IOException("SD Card is not mounted.  It is " + state + ".");
		    }
		} catch (IOException e) {
			MTalkLogWriter.e(e);
			return null;
		}
		
		String path = null;
	    if(isBuddy == 1){	// 버디 
	    	path = MTalkConstant.MTALK_PATH + "/buddy";
	    }else {				// 프로필 
	    	path = MTalkConstant.MTALK_PATH + "/profile";
	    }
	    
		File directory = new File(path);
		if(!directory.exists()){
			directory.mkdirs();
		}
		if (!directory.exists() && !directory.mkdirs()) {
			try {
				throw new IOException("Path to mFile could not be created.");
			} catch (IOException e) {
				MTalkLogWriter.e(e);
				return null;
			}
		}
		//String dateString = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss-SSSZ").format(new Date());
		path += "/"+GpbSupport.UuidMsgToString(mProfileUuid);//"/"+dateString+"_"+fileName;

		return path;
	}

	protected void downloadEachSlice(int sliceNo, int numOfSlice, int sliceSize) {
		// 요청 구성
		DownloadReq downloadReq = new DownloadReq();
		downloadReq.setFileKey(mTransferInfo.getFileKey());
		downloadReq.setSliceNo(sliceNo);
		if (mImageOption != null) {
			downloadReq.setImageOption(mImageOption);
		}
		// envelope 구성
		TcpEnvelope tcpEnvelope = new TcpEnvelope.Builder()
		.setMethodId(MethodId.FileServer.File.Download)
		.setType(TcpEnvelope.REQUEST)
		.setMessage(downloadReq)
		.setCallback(mCallback)
		.build();
		// 발송
		FileClient.send(tcpEnvelope);
	}
	
	protected void downloadResultReceived(DownloadRes res) {
		int resSliceNo = res.getSliceNo();
		
		int responseCode = res.getResponseCode();
		switch (responseCode) {
		case Common.SUCCESS:
			try {
				// 현재 슬라이스를 파일에 기록
				RandomAccessFile randomAccessFile = new RandomAccessFile(getFile(), "rw");
				byte[] sliceArray = res.getFileData().toByteArray();	// 현재 처리중인 슬라이스를 담을 임시 버퍼
				if (sliceArray.length != res.getDataSize()) {
					MTalkLogWriter.e("FileDownloadTask.downloadResultReceived(): Wrong data size.");
					mTransferInfo.setWorking(false);
					// 상태 저장. 에러이므로 반드시.
					updateNonvolatileInfo();
					mUiHandler.sendMessageAtFrontOfQueue(mUiHandler.obtainMessage(HandlerMsgConst.HANDLER_PROFILE_DOWNLOAD_FAIL));
					return;
				}
				randomAccessFile.seek(resSliceNo*mTransferInfo.getSliceSize());
				randomAccessFile.write(sliceArray);
				randomAccessFile.close();
				mTransferInfo.setState(Database.SENDING_SLICE);
				// 지금까지 받은 슬라이스 번호를 추출
				mTransferInfo.setSliceNo(Math.max(mTransferInfo.getSliceNo(), resSliceNo));
				// 상태 저장. 재전송 효율을 따지거나 진행상태를 보여주고자 한다면 있어야 함.
				updateNonvolatileInfo();
				MTalkLogWriter.d("FileDownloadTask.downloadResultReceived(): downloading "+mTransferInfo.getSliceNo()+"/"+mTransferInfo.getNumOfSlice()+". (+"+sliceArray.length+" bytes) success");
				// 즉시 메모리 반환
				sliceArray = null;
				System.gc();

				// 다음 슬라이스 처리
				if (mTransferInfo.getSliceNo()+1 < mTransferInfo.getNumOfSlice()) {	// 보낼 게 남았다면
					downloadEachSlice(mTransferInfo.getSliceNo()+1, mTransferInfo.getNumOfSlice(), mTransferInfo.getSliceSize());	// 다음 슬라이스를 보낸다.
				} else {	// 파일 전체가 다 간 거라면
					downloadFinished();
				}
			} catch (Exception e) {
				MTalkLogWriter.e(e);
				mTransferInfo.setWorking(false);
				// 상태 저장. 에러이므로 반드시.
				updateNonvolatileInfo();
				mUiHandler.sendMessageAtFrontOfQueue(mUiHandler.obtainMessage(HandlerMsgConst.HANDLER_PROFILE_DOWNLOAD_FAIL));
			}
			break;
		default:
			mTransferInfo.setWorking(false);
			// 상태 저장. 에러이므로 반드시.
			updateNonvolatileInfo();
			mUiHandler.sendMessageAtFrontOfQueue(mUiHandler.obtainMessage(HandlerMsgConst.HANDLER_PROFILE_DOWNLOAD_FAIL));
			MTalkLogWriter.e("FileDownloadTask.downloadResultReceived: Fail with responseCode "+responseCode);
			break;
		}
	}

	protected void downloadFinished() {
		mTransferInfo.setState(Database.SENDING_MESSAGE);	// 의미상 맞지 않으나 업로드와 같은 조건으로 끝나게 하기 위해.
		mTransferInfo.setWorking(false);
		// 상태 저장. 완료되었으므로 반드시.
		updateNonvolatileInfo();
		
		Message msg = mUiHandler.obtainMessage(HandlerMsgConst.HANDLER_PROFILE_DOWNLOAD_SUCCESS);
		msg.obj = mTransferInfo.getFileUri();
		mUiHandler.sendMessageAtFrontOfQueue(msg);
		
		ProfileDatabaseHelper.removeTransferInfo(mContext, mProfileUuid, MODE_DOWNLOAD);
		if(isBuddy == 1){
			BuddyDatabaseHelper.updateBuddyImage(mContext, mProfileUuid, false);
			ProfileImageLoader.getInstance(mContext).deleteCache(mProfileUuid);
//			ProfileImageLoader.getInstance(mContext).deleteImage(mProfileUuid);
		}
	}
}
